import { useEffect, useRef, useState } from 'react'
import { getMusicIndexByNote, getMusicKeyByNote } from '@/lib/music/data'
import styles from './scss/musicSpectrun.module.scss'
import pianoPlayerEvent from '@/event/pianoPlayerEvent'
import noteSvg from '@/assets/svg/note.svg'
import problemSvg from '@/assets/svg/problem.svg'
import clearSvg from '@/assets/svg/clear.svg'
import backSvg from '@/assets/svg/back.svg'
import { musicBasicSettingStore } from '@/store/musicBasicSettingStore'
import { observer } from 'mobx-react'
import { MusicPlayerSong } from '@/types/music'
import { MessageBox } from '@/utils/messageBox'
import { combinationNotesHandler } from '@/lib/music/musicNote'
import { stylesList } from '@/utils/common'
interface Props {
  // song: MusicPlayerSong
}

export type MusicSpectrumShowState = 'note' | 'keyboard' | 'pianoCode'

class PianoCode {
  public spot1: boolean = false
  public spot2: boolean = false
  public spot3: boolean = false
  public spot4: boolean = false
  public spot5: boolean = false
  public code: string = '1'
  private static blackNumbers = [1, 2, 4, 5, 6]
  private constructor(note: string) {
    const { index, type } = getMusicIndexByNote(note)
    if (type == 'white') {
      if (index < 7) {
        this.spot4 = true
        this.spot5 = true
      } else if (index < 14) {
        this.spot4 = true
      } else if (index < 21) {
        this.spot3 = false
      } else if (index < 28) {
        this.spot3 = true
      } else if (index < 35) {
        this.spot2 = true
        this.spot3 = true
      } else if (index == 35) {
        this.spot1 = true
        this.spot2 = true
        this.spot3 = true
      }
      this.code = `${(index % 7) + 1}`
    } else {
      if (index < 5) {
        this.spot4 = true
        this.spot5 = true
      } else if (index < 10) {
        this.spot4 = true
      } else if (index < 15) {
      } else if (index < 20) {
        this.spot3 = true
      } else if (index < 25) {
        this.spot2 = true
        this.spot3 = true
      }
      this.code = `${PianoCode.blackNumbers[index % 5]}#`
    }
  }
  private static cache = new Map<string, PianoCode>()
  public static getPianoCode(note: string) {
    if (!PianoCode.cache.has(note)) {
      PianoCode.cache.set(note, new PianoCode(note))
    }
    return PianoCode.cache.get(note)!
  }
}

const menu = [
  {
    name: '音符',
    value: 'note',
  },
  {
    name: '键盘键位',
    value: 'keyboard',
  },
  {
    name: '数字简谱',
    value: 'pianoCode',
  },
] as { name: string; value: MusicSpectrumShowState }[]

function showHelp(state: MusicSpectrumShowState) {
  let content = ''
  switch (state) {
    case 'note':
      content = `音乐中基本的音高名称用字母来表示，分别为 C、D、E、F、G、A、B。后面的数字表示音高，数字越大音高越高
用[  ]包裹的表示同时按下`
      break
    case 'keyboard':
      content = `每个符号对应一个键盘键位
用[  ]包裹的表示同时按下`
      break
    case 'pianoCode':
      content = `每个符号对应一个数字简谱
用[  ]包裹的表示同时按下`
      break
  }
  MessageBox.info('提示', content)
}

const spotStyle = styles.spot
const spotHideStyle = stylesList(styles, 'spot hide')

/**
 * 限制显示的音符数量
 */
const PageNoteSize = 150

function pageDivision(
  notes: MusicPlayerSong['data'][],
  page: number
): MusicPlayerSong['data'][] {
  return notes.slice((page - 1) * PageNoteSize, page * PageNoteSize)
}
function pageCurrentPage(
  notes: MusicPlayerSong['data'],
  currentTime: number
): number {
  const notes2 = combinationNotesHandler(notes)
  for (let i = 0; i < notes2.length; i++) {
    if (notes2[i][0].time >= currentTime) {
      return Math.floor(i / PageNoteSize) + 1
    }
  }
  return Math.floor(notes2.length / PageNoteSize) + 1
}
function getPageCount(notes: MusicPlayerSong['data']): number {
  const notes2 = combinationNotesHandler(notes)
  return Math.floor(notes2.length / PageNoteSize) + 1
}
function getNoteIndex(
  notes: MusicPlayerSong['data'],
  currentTime: number
): number {
  for (let i = 0; i < notes.length; i++) {
    if (notes[i].time >= currentTime) {
      return i
    }
  }
  return notes.length
}

const sortNote = (n1: string, n2: string) => {
  const k1 = getMusicIndexByNote(n1)
  const k2 = getMusicIndexByNote(n2)
  return k1.index - k2.index
}
/**
 * 乐谱
 */
function MusicSpectrum(_props: Props) {
  const [currentTime, setCurrentTime] = useState(0)
  const [activeSong, setActiveSong] = useState<MusicPlayerSong | null>(null)
  const [nowNoteList, setNowNoteList] = useState<MusicPlayerSong['data']>([])
  const [_, setPage] = useState(1)
  const [showPage, setShowPage] = useState(1)
  const startNoteTime = useRef(0)
  useEffect(() => {
    if (activeSong) {
      setPage((page) => {
        const page2 = pageCurrentPage(activeSong.data, currentTime)
        if (page2 != page) {
          setShowPage(page2)
        }
        return page2
      })
    }
  }, [activeSong, currentTime])
  useEffect(() => {
    setPage((page) => {
      const page2 = pageCurrentPage(nowNoteList, currentTime)
      if (page2 != page) {
        setShowPage(page2)
      }
      return page2
    })
  }, [nowNoteList])

  useEffect(() => {
    const songPackage = { value: null, currentTime: 0 } as {
      value: MusicPlayerSong | null
      currentTime: number
    }
    pianoPlayerEvent.emit('loadSong', songPackage)
    if (songPackage.value) {
      setActiveSong(songPackage.value)
      // console.log(songPackage.currentTime)
      setTimeout(() => setCurrentTime(songPackage.currentTime), 0)
    }
    const u1 = pianoPlayerEvent.on('playSongTimeUpdate', (t) => {
      setCurrentTime(t)
    })
    const u2 = pianoPlayerEvent.on('playerSong', (e) => {
      setActiveSong(e)
    })
    const u3 = pianoPlayerEvent.on('onPlayerSongStop', () => {
      setActiveSong(null)
    })
    return () => {
      u1()
      u2()
      u3()
    }
  }, [])
  useEffect(() => {
    if (!activeSong) {
      setNowNoteList([])
      setCurrentTime(Date.now())
      const u1 = pianoPlayerEvent.on('noteDown', (e) => {
        setNowNoteList((list) => {
          if (list.length == 0) {
            startNoteTime.current = Date.now()
            return [
              {
                note: e.music,
                time: 0,
              },
            ]
          }
          return [
            ...list,
            {
              note: e.music,
              time: Date.now() - startNoteTime.current,
            },
          ]
        })
      })
      return () => {
        u1()
      }
    }
    setPage(1)
    setShowPage(1)
  }, [activeSong])
  const originNotes = activeSong?.data || []
  if (!activeSong) {
    originNotes.push(...nowNoteList)
  }
  const combinationNotes: MusicPlayerSong['data'][] = pageDivision(
    combinationNotesHandler(originNotes),
    showPage
  )
  let pageCount = 0
  if (activeSong) {
    pageCount = getPageCount(activeSong.data)
  } else {
    pageCount = getPageCount(nowNoteList)
  }

  function showValuse(notes: string[]) {
    let v = ''
    switch (musicBasicSettingStore.value.musicSpectrumState) {
      case 'note':
        v = notes.sort(sortNote).join(' ')
        break
      case 'keyboard':
        v = notes
          .sort(sortNote)
          .map((item) => getMusicKeyByNote(item)?.keyboard)
          .join(' ')
        break
      case 'pianoCode':
        if (notes.length == 1) {
          const code = PianoCode.getPianoCode(notes[0])
          return (
            <div className={styles['special-box']}>
              <div className={code.spot1 ? spotStyle : spotHideStyle}></div>
              <div className={code.spot2 ? spotStyle : spotHideStyle}></div>
              <div className={code.spot3 ? spotStyle : spotHideStyle}></div>
              <div className={styles.value}>{code.code}</div>
              <div className={code.spot4 ? spotStyle : spotHideStyle}></div>
              <div className={code.spot5 ? spotStyle : spotHideStyle}></div>
            </div>
          )
        }
        return (
          <div className={styles['special-box-list']}>
            {notes.sort(sortNote).map((note, key) => {
              const code = PianoCode.getPianoCode(note)
              return (
                <div className={styles['special-box']} key={key}>
                  <div className={code.spot1 ? spotStyle : spotHideStyle}></div>
                  <div className={code.spot2 ? spotStyle : spotHideStyle}></div>
                  <div className={code.spot3 ? spotStyle : spotHideStyle}></div>
                  <div className={styles.value}>{code.code}</div>
                  <div className={code.spot4 ? spotStyle : spotHideStyle}></div>
                  <div className={code.spot5 ? spotStyle : spotHideStyle}></div>
                </div>
              )
            })}
          </div>
        )
    }
    if (notes.length == 1) {
      return v
    }
    return `[${v}]`
  }

  function getSpaceWidth(index: number) {
    if (index == 0) {
      return 0
    }
    const lastItem = combinationNotes[index - 1]
    const item = combinationNotes[index]
    const druation = item[0].time - lastItem[lastItem.length - 1].time
    if (druation < 1000) {
      return 0
    }
    return Math.min((druation - 1000) * 0.04, 100)
  }

  return (
    <div className={styles['music-spectrun-box']}>
      <div className={styles['top-box']}>
        {!activeSong && (
          <div className={styles['clear']} onClick={() => setNowNoteList([])}>
            <img src={clearSvg.src} />
          </div>
        )}

        <div className={styles['state']}>
          {menu.map((item, index) => {
            return (
              <div
                key={index}
                className={`${styles['state-item']}${
                  musicBasicSettingStore.value.musicSpectrumState === item.value
                    ? ` ${styles['state-item-active']}`
                    : ''
                }`}
                onClick={() =>
                  musicBasicSettingStore.setValue(
                    'musicSpectrumState',
                    item.value
                  )
                }>
                {item.name}
              </div>
            )
          })}
          <div>
            <img
              src={problemSvg.src}
              className={styles['problem-icon']}
              onClick={() =>
                showHelp(musicBasicSettingStore.value.musicSpectrumState)
              }
            />
          </div>
        </div>
      </div>
      {combinationNotes.length > 0 && (
        <>
          <div className={styles['music-spectrun']}>
            {combinationNotes.map((item, index) => {
              return (
                <div
                  key={index}
                  className={styles['music-spectrum-item-wapper']}>
                  <div
                    style={{
                      width: `${getSpaceWidth(index)}px`,
                    }}
                    className={styles['music-spectrum-item-space']}></div>
                  <div
                    className={`${styles['music-spectrum-item']}${
                      currentTime >= item[0].time
                        ? ` ${styles['music-spectrum-item-active']}`
                        : ''
                    }`}>
                    <div>{showValuse(item.map((item) => item.note))}</div>
                  </div>
                </div>
              )
            })}
          </div>
          <div className={styles['page-box']}>
            <div
              className={styles['btn']}
              onClick={() => showPage !== 1 && setShowPage(showPage - 1)}>
              <img
                src={backSvg.src}
                style={{ opacity: showPage == 1 ? '0.5' : '1' }}></img>
            </div>
            {`${showPage}/${pageCount}`}
            <div
              className={styles['btn']}
              onClick={() => showPage < pageCount && setShowPage(showPage + 1)}>
              <img
                src={backSvg.src}
                style={{
                  transform: 'rotate(180deg)',
                  opacity: showPage == pageCount ? '0.5' : '1',
                }}></img>
            </div>
          </div>
          {activeSong && (
            <div className={styles['tips-box']}>
              <div className={styles['tips']}>{`时间：${(
                currentTime / 1000
              ).toFixed(0)} / ${(activeSong.playerTime / 1000).toFixed(
                0
              )}s`}</div>
              <div className={styles['tips']}>{`音符：${getNoteIndex(
                activeSong.data,
                currentTime
              )} / ${activeSong.data.length}`}</div>
            </div>
          )}
        </>
      )}
      {combinationNotes.length == 0 && (
        <div className={styles['music-spectrun-empty-box']}>
          <img src={noteSvg.src} className={styles['music-spectrun-empty']} />
          <div className={styles['music-spectrun-empty-text']}>暂无数据</div>
        </div>
      )}
    </div>
  )
}

export default observer(MusicSpectrum)
